home *** CD-ROM | disk | FTP | other *** search
/ Freelog 125 / Freelog_MarsAvril2015_No125.iso / ViePratique / ArchiFacile / ArchiFacileSetup.exe / {app} / nw.pak / Unnamed File 001147.txt < prev    next >
Text File  |  2014-10-14  |  63KB  |  1,971 lines

  1. // Copyright (c) 2013 The Chromium Authors. All rights reserved.
  2. // Use of this source code is governed by a BSD-style license that can be
  3. // found in the LICENSE file.
  4.  
  5. var tabView = null;
  6. var ssrcInfoManager = null;
  7. var peerConnectionUpdateTable = null;
  8. var statsTable = null;
  9. var dumpCreator = null;
  10. /** A map from peer connection id to the PeerConnectionRecord. */
  11. var peerConnectionDataStore = {};
  12. /** A list of getUserMedia requests. */
  13. var userMediaRequests = [];
  14.  
  15. /** A simple class to store the updates and stats data for a peer connection. */
  16. var PeerConnectionRecord = (function() {
  17.   /** @constructor */
  18.   function PeerConnectionRecord() {
  19.     /** @private */
  20.     this.record_ = {
  21.       constraints: {},
  22.       servers: [],
  23.       stats: {},
  24.       updateLog: [],
  25.       url: '',
  26.     };
  27.   };
  28.  
  29.   PeerConnectionRecord.prototype = {
  30.     /** @override */
  31.     toJSON: function() {
  32.       return this.record_;
  33.     },
  34.  
  35.     /**
  36.      * Adds the initilization info of the peer connection.
  37.      * @param {string} url The URL of the web page owning the peer connection.
  38.      * @param {Array} servers STUN servers used by the peer connection.
  39.      * @param {!Object} constraints Media constraints.
  40.      */
  41.     initialize: function(url, servers, constraints) {
  42.       this.record_.url = url;
  43.       this.record_.servers = servers;
  44.       this.record_.constraints = constraints;
  45.     },
  46.  
  47.     /**
  48.      * @param {string} dataSeriesId The TimelineDataSeries identifier.
  49.      * @return {!TimelineDataSeries}
  50.      */
  51.     getDataSeries: function(dataSeriesId) {
  52.       return this.record_.stats[dataSeriesId];
  53.     },
  54.  
  55.     /**
  56.      * @param {string} dataSeriesId The TimelineDataSeries identifier.
  57.      * @param {!TimelineDataSeries} dataSeries The TimelineDataSeries to set to.
  58.      */
  59.     setDataSeries: function(dataSeriesId, dataSeries) {
  60.       this.record_.stats[dataSeriesId] = dataSeries;
  61.     },
  62.  
  63.     /**
  64.      * @param {string} type The type of the update.
  65.      * @param {string} value The value of the update.
  66.      */
  67.     addUpdate: function(type, value) {
  68.       this.record_.updateLog.push({
  69.         time: (new Date()).toLocaleString(),
  70.         type: type,
  71.         value: value,
  72.       });
  73.     },
  74.   };
  75.  
  76.   return PeerConnectionRecord;
  77. })();
  78.  
  79. // The maximum number of data points bufferred for each stats. Old data points
  80. // will be shifted out when the buffer is full.
  81. var MAX_STATS_DATA_POINT_BUFFER_SIZE = 1000;
  82.  
  83. // Copyright 2013 The Chromium Authors. All rights reserved.
  84. // Use of this source code is governed by a BSD-style license that can be
  85. // found in the LICENSE file.
  86.  
  87. /**
  88.  * A TabView provides the ability to create tabs and switch between tabs. It's
  89.  * responsible for creating the DOM and managing the visibility of each tab.
  90.  * The first added tab is active by default and the others hidden.
  91.  */
  92. var TabView = (function() {
  93.   'use strict';
  94.  
  95.   /**
  96.    * @constructor
  97.    * @param {Element} root The root DOM element containing the tabs.
  98.    */
  99.   function TabView(root) {
  100.     this.root_ = root;
  101.     this.ACTIVE_TAB_HEAD_CLASS_ = 'active-tab-head';
  102.     this.ACTIVE_TAB_BODY_CLASS_ = 'active-tab-body';
  103.     this.TAB_HEAD_CLASS_ = 'tab-head';
  104.     this.TAB_BODY_CLASS_ = 'tab-body';
  105.  
  106.     /**
  107.      * A mapping for an id to the tab elements.
  108.      * @type {!Object<string, !TabDom>}
  109.      * @private
  110.      */
  111.     this.tabElements_ = {};
  112.  
  113.     this.headBar_ = null;
  114.     this.activeTabId_ = null;
  115.     this.initializeHeadBar_();
  116.   }
  117.  
  118.   // Creates a simple object containing the tab head and body elements.
  119.   function TabDom(h, b) {
  120.     this.head = h;
  121.     this.body = b;
  122.   }
  123.  
  124.   TabView.prototype = {
  125.     /**
  126.      * Adds a tab with the specified id and title.
  127.      * @param {string} id
  128.      * @param {string} title
  129.      * @return {!Element} The tab body element.
  130.      */
  131.     addTab: function(id, title) {
  132.       if (this.tabElements_[id])
  133.         throw 'Tab already exists: ' + id;
  134.  
  135.       var head = document.createElement('div');
  136.       head.className = this.TAB_HEAD_CLASS_;
  137.       head.textContent = title + ' [' + id + ']';
  138.       head.title = title;
  139.       this.headBar_.appendChild(head);
  140.       head.addEventListener('click', this.switchTab_.bind(this, id));
  141.  
  142.       var body = document.createElement('div');
  143.       body.className = this.TAB_BODY_CLASS_;
  144.       body.id = id;
  145.       this.root_.appendChild(body);
  146.  
  147.       this.tabElements_[id] = new TabDom(head, body);
  148.  
  149.       if (!this.activeTabId_) {
  150.         this.switchTab_(id);
  151.       }
  152.       return this.tabElements_[id].body;
  153.     },
  154.  
  155.     /** Removes the tab. @param {string} id */
  156.     removeTab: function(id) {
  157.       if (!this.tabElements_[id])
  158.         return;
  159.       this.tabElements_[id].head.parentNode.removeChild(
  160.           this.tabElements_[id].head);
  161.       this.tabElements_[id].body.parentNode.removeChild(
  162.           this.tabElements_[id].body);
  163.  
  164.       delete this.tabElements_[id];
  165.       if (this.activeTabId_ == id) {
  166.         this.switchTab_(Object.keys(this.tabElements_)[0]);
  167.       }
  168.     },
  169.  
  170.     /**
  171.      * Switches the specified tab into view.
  172.      *
  173.      * @param {string} activeId The id the of the tab that should be switched to
  174.      *     active state.
  175.      * @private
  176.      */
  177.     switchTab_: function(activeId) {
  178.       if (this.activeTabId_ && this.tabElements_[this.activeTabId_]) {
  179.         this.tabElements_[this.activeTabId_].body.classList.remove(
  180.             this.ACTIVE_TAB_BODY_CLASS_);
  181.         this.tabElements_[this.activeTabId_].head.classList.remove(
  182.             this.ACTIVE_TAB_HEAD_CLASS_);
  183.       }
  184.       this.activeTabId_ = activeId;
  185.       if (this.tabElements_[activeId]) {
  186.         this.tabElements_[activeId].body.classList.add(
  187.             this.ACTIVE_TAB_BODY_CLASS_);
  188.         this.tabElements_[activeId].head.classList.add(
  189.             this.ACTIVE_TAB_HEAD_CLASS_);
  190.       }
  191.     },
  192.  
  193.     /** Initializes the bar containing the tab heads. */
  194.     initializeHeadBar_: function() {
  195.       this.headBar_ = document.createElement('div');
  196.       this.root_.appendChild(this.headBar_);
  197.       this.headBar_.style.textAlign = 'center';
  198.     },
  199.   };
  200.   return TabView;
  201. })();
  202.  
  203. // Copyright (c) 2013 The Chromium Authors. All rights reserved.
  204. // Use of this source code is governed by a BSD-style license that can be
  205. // found in the LICENSE file.
  206.  
  207. /**
  208.  * A TimelineDataSeries collects an ordered series of (time, value) pairs,
  209.  * and converts them to graph points.  It also keeps track of its color and
  210.  * current visibility state.
  211.  * It keeps MAX_STATS_DATA_POINT_BUFFER_SIZE data points at most. Old data
  212.  * points will be dropped when it reaches this size.
  213.  */
  214. var TimelineDataSeries = (function() {
  215.   'use strict';
  216.  
  217.   /**
  218.    * @constructor
  219.    */
  220.   function TimelineDataSeries() {
  221.     // List of DataPoints in chronological order.
  222.     this.dataPoints_ = [];
  223.  
  224.     // Default color.  Should always be overridden prior to display.
  225.     this.color_ = 'red';
  226.     // Whether or not the data series should be drawn.
  227.     this.isVisible_ = true;
  228.  
  229.     this.cacheStartTime_ = null;
  230.     this.cacheStepSize_ = 0;
  231.     this.cacheValues_ = [];
  232.   }
  233.  
  234.   TimelineDataSeries.prototype = {
  235.     /**
  236.      * @override
  237.      */
  238.     toJSON: function() {
  239.       if (this.dataPoints_.length < 1)
  240.         return {};
  241.  
  242.       var values = [];
  243.       for (var i = 0; i < this.dataPoints_.length; ++i) {
  244.         values.push(this.dataPoints_[i].value);
  245.       }
  246.       return {
  247.         startTime: this.dataPoints_[0].time,
  248.         endTime: this.dataPoints_[this.dataPoints_.length - 1].time,
  249.         values: JSON.stringify(values),
  250.       };
  251.     },
  252.  
  253.     /**
  254.      * Adds a DataPoint to |this| with the specified time and value.
  255.      * DataPoints are assumed to be received in chronological order.
  256.      */
  257.     addPoint: function(timeTicks, value) {
  258.       var time = new Date(timeTicks);
  259.       this.dataPoints_.push(new DataPoint(time, value));
  260.  
  261.       if (this.dataPoints_.length > MAX_STATS_DATA_POINT_BUFFER_SIZE)
  262.         this.dataPoints_.shift();
  263.     },
  264.  
  265.     isVisible: function() {
  266.       return this.isVisible_;
  267.     },
  268.  
  269.     show: function(isVisible) {
  270.       this.isVisible_ = isVisible;
  271.     },
  272.  
  273.     getColor: function() {
  274.       return this.color_;
  275.     },
  276.  
  277.     setColor: function(color) {
  278.       this.color_ = color;
  279.     },
  280.  
  281.     getCount: function() {
  282.       return this.dataPoints_.length;
  283.     },
  284.     /**
  285.      * Returns a list containing the values of the data series at |count|
  286.      * points, starting at |startTime|, and |stepSize| milliseconds apart.
  287.      * Caches values, so showing/hiding individual data series is fast.
  288.      */
  289.     getValues: function(startTime, stepSize, count) {
  290.       // Use cached values, if we can.
  291.       if (this.cacheStartTime_ == startTime &&
  292.           this.cacheStepSize_ == stepSize &&
  293.           this.cacheValues_.length == count) {
  294.         return this.cacheValues_;
  295.       }
  296.  
  297.       // Do all the work.
  298.       this.cacheValues_ = this.getValuesInternal_(startTime, stepSize, count);
  299.       this.cacheStartTime_ = startTime;
  300.       this.cacheStepSize_ = stepSize;
  301.  
  302.       return this.cacheValues_;
  303.     },
  304.  
  305.     /**
  306.      * Returns the cached |values| in the specified time period.
  307.      */
  308.     getValuesInternal_: function(startTime, stepSize, count) {
  309.       var values = [];
  310.       var nextPoint = 0;
  311.       var currentValue = 0;
  312.       var time = startTime;
  313.       for (var i = 0; i < count; ++i) {
  314.         while (nextPoint < this.dataPoints_.length &&
  315.                this.dataPoints_[nextPoint].time < time) {
  316.           currentValue = this.dataPoints_[nextPoint].value;
  317.           ++nextPoint;
  318.         }
  319.         values[i] = currentValue;
  320.         time += stepSize;
  321.       }
  322.       return values;
  323.     }
  324.   };
  325.  
  326.   /**
  327.    * A single point in a data series.  Each point has a time, in the form of
  328.    * milliseconds since the Unix epoch, and a numeric value.
  329.    * @constructor
  330.    */
  331.   function DataPoint(time, value) {
  332.     this.time = time;
  333.     this.value = value;
  334.   }
  335.  
  336.   return TimelineDataSeries;
  337. })();
  338.  
  339. // Copyright (c) 2013 The Chromium Authors. All rights reserved.
  340. // Use of this source code is governed by a BSD-style license that can be
  341. // found in the LICENSE file.
  342.  
  343.  
  344.  
  345. /**
  346.  * Get the ssrc if |report| is an ssrc report.
  347.  *
  348.  * @param {!Object} report The object contains id, type, and stats, where stats
  349.  *     is the object containing timestamp and values, which is an array of
  350.  *     strings, whose even index entry is the name of the stat, and the odd
  351.  *     index entry is the value.
  352.  * @return {?string} The ssrc.
  353.  */
  354. function GetSsrcFromReport(report) {
  355.   if (report.type != 'ssrc') {
  356.     console.warn("Trying to get ssrc from non-ssrc report.");
  357.     return null;
  358.   }
  359.  
  360.   // If the 'ssrc' name-value pair exists, return the value; otherwise, return
  361.   // the report id.
  362.   // The 'ssrc' name-value pair only exists in an upcoming Libjingle change. Old
  363.   // versions use id to refer to the ssrc.
  364.   //
  365.   // TODO(jiayl): remove the fallback to id once the Libjingle change is rolled
  366.   // to Chrome.
  367.   if (report.stats && report.stats.values) {
  368.     for (var i = 0; i < report.stats.values.length - 1; i += 2) {
  369.       if (report.stats.values[i] == 'ssrc') {
  370.         return report.stats.values[i + 1];
  371.       }
  372.     }
  373.   }
  374.   return report.id;
  375. };
  376.  
  377. /**
  378.  * SsrcInfoManager stores the ssrc stream info extracted from SDP.
  379.  */
  380. var SsrcInfoManager = (function() {
  381.   'use strict';
  382.  
  383.   /**
  384.    * @constructor
  385.    */
  386.   function SsrcInfoManager() {
  387.     /**
  388.      * Map from ssrc id to an object containing all the stream properties.
  389.      * @type {!Object.<string, !Object.<string>>}
  390.      * @private
  391.      */
  392.     this.streamInfoContainer_ = {};
  393.  
  394.     /**
  395.      * The string separating attibutes in an SDP.
  396.      * @type {string}
  397.      * @const
  398.      * @private
  399.      */
  400.     this.ATTRIBUTE_SEPARATOR_ = /[\r,\n]/;
  401.  
  402.     /**
  403.      * The regex separating fields within an ssrc description.
  404.      * @type {RegExp}
  405.      * @const
  406.      * @private
  407.      */
  408.     this.FIELD_SEPARATOR_REGEX_ = / .*:/;
  409.  
  410.     /**
  411.      * The prefix string of an ssrc description.
  412.      * @type {string}
  413.      * @const
  414.      * @private
  415.      */
  416.     this.SSRC_ATTRIBUTE_PREFIX_ = 'a=ssrc:';
  417.  
  418.     /**
  419.      * The className of the ssrc info parent element.
  420.      * @type {string}
  421.      * @const
  422.      */
  423.     this.SSRC_INFO_BLOCK_CLASS = 'ssrc-info-block';
  424.   }
  425.  
  426.   SsrcInfoManager.prototype = {
  427.     /**
  428.      * Extracts the stream information from |sdp| and saves it.
  429.      * For example:
  430.      *     a=ssrc:1234 msid:abcd
  431.      *     a=ssrc:1234 label:hello
  432.      *
  433.      * @param {string} sdp The SDP string.
  434.      */
  435.     addSsrcStreamInfo: function(sdp) {
  436.       var attributes = sdp.split(this.ATTRIBUTE_SEPARATOR_);
  437.       for (var i = 0; i < attributes.length; ++i) {
  438.         // Check if this is a ssrc attribute.
  439.         if (attributes[i].indexOf(this.SSRC_ATTRIBUTE_PREFIX_) != 0)
  440.           continue;
  441.  
  442.         var nextFieldIndex = attributes[i].search(this.FIELD_SEPARATOR_REGEX_);
  443.  
  444.         if (nextFieldIndex == -1)
  445.           continue;
  446.  
  447.         var ssrc = attributes[i].substring(this.SSRC_ATTRIBUTE_PREFIX_.length,
  448.                                            nextFieldIndex);
  449.         if (!this.streamInfoContainer_[ssrc])
  450.           this.streamInfoContainer_[ssrc] = {};
  451.  
  452.         // Make |rest| starting at the next field.
  453.         var rest = attributes[i].substring(nextFieldIndex + 1);
  454.         var name, value;
  455.         while (rest.length > 0) {
  456.           nextFieldIndex = rest.search(this.FIELD_SEPARATOR_REGEX_);
  457.           if (nextFieldIndex == -1)
  458.             nextFieldIndex = rest.length;
  459.  
  460.           // The field name is the string before the colon.
  461.           name = rest.substring(0, rest.indexOf(':'));
  462.           // The field value is from after the colon to the next field.
  463.           value = rest.substring(rest.indexOf(':') + 1, nextFieldIndex);
  464.           this.streamInfoContainer_[ssrc][name] = value;
  465.  
  466.           // Move |rest| to the start of the next field.
  467.           rest = rest.substring(nextFieldIndex + 1);
  468.         }
  469.       }
  470.     },
  471.  
  472.     /**
  473.      * @param {string} sdp The ssrc id.
  474.      * @return {!Object.<string>} The object containing the ssrc infomation.
  475.      */
  476.     getStreamInfo: function(ssrc) {
  477.       return this.streamInfoContainer_[ssrc];
  478.     },
  479.  
  480.     /**
  481.      * Populate the ssrc information into |parentElement|, each field as a
  482.      * DIV element.
  483.      *
  484.      * @param {!Element} parentElement The parent element for the ssrc info.
  485.      * @param {string} ssrc The ssrc id.
  486.      */
  487.     populateSsrcInfo: function(parentElement, ssrc) {
  488.       if (!this.streamInfoContainer_[ssrc])
  489.         return;
  490.  
  491.       parentElement.className = this.SSRC_INFO_BLOCK_CLASS;
  492.  
  493.       var fieldElement;
  494.       for (var property in this.streamInfoContainer_[ssrc]) {
  495.         fieldElement = document.createElement('div');
  496.         parentElement.appendChild(fieldElement);
  497.         fieldElement.textContent =
  498.             property + ':' + this.streamInfoContainer_[ssrc][property];
  499.       }
  500.     }
  501.   };
  502.  
  503.   return SsrcInfoManager;
  504. })();
  505.  
  506. // Copyright (c) 2013 The Chromium Authors. All rights reserved.
  507. // Use of this source code is governed by a BSD-style license that can be
  508. // found in the LICENSE file.
  509.  
  510. //
  511. // This file contains helper methods to draw the stats timeline graphs.
  512. // Each graph represents a series of stats report for a PeerConnection,
  513. // e.g. 1234-0-ssrc-abcd123-bytesSent is the graph for the series of bytesSent
  514. // for ssrc-abcd123 of PeerConnection 0 in process 1234.
  515. // The graphs are drawn as CANVAS, grouped per report type per PeerConnection.
  516. // Each group has an expand/collapse button and is collapsed initially.
  517. //
  518.  
  519. // Copyright (c) 2013 The Chromium Authors. All rights reserved.
  520. // Use of this source code is governed by a BSD-style license that can be
  521. // found in the LICENSE file.
  522.  
  523. /**
  524.  * A TimelineGraphView displays a timeline graph on a canvas element.
  525.  */
  526. var TimelineGraphView = (function() {
  527.   'use strict';
  528.  
  529.   // Maximum number of labels placed vertically along the sides of the graph.
  530.   var MAX_VERTICAL_LABELS = 6;
  531.  
  532.   // Vertical spacing between labels and between the graph and labels.
  533.   var LABEL_VERTICAL_SPACING = 4;
  534.   // Horizontal spacing between vertically placed labels and the edges of the
  535.   // graph.
  536.   var LABEL_HORIZONTAL_SPACING = 3;
  537.   // Horizintal spacing between two horitonally placed labels along the bottom
  538.   // of the graph.
  539.   var LABEL_LABEL_HORIZONTAL_SPACING = 25;
  540.  
  541.   // Length of ticks, in pixels, next to y-axis labels.  The x-axis only has
  542.   // one set of labels, so it can use lines instead.
  543.   var Y_AXIS_TICK_LENGTH = 10;
  544.  
  545.   var GRID_COLOR = '#CCC';
  546.   var TEXT_COLOR = '#000';
  547.   var BACKGROUND_COLOR = '#FFF';
  548.  
  549.   var MAX_DECIMAL_PRECISION = 2;
  550.   /**
  551.    * @constructor
  552.    */
  553.   function TimelineGraphView(divId, canvasId) {
  554.     this.scrollbar_ = {position_: 0, range_: 0};
  555.  
  556.     this.graphDiv_ = $(divId);
  557.     this.canvas_ = $(canvasId);
  558.  
  559.     // Set the range and scale of the graph.  Times are in milliseconds since
  560.     // the Unix epoch.
  561.  
  562.     // All measurements we have must be after this time.
  563.     this.startTime_ = 0;
  564.     // The current rightmost position of the graph is always at most this.
  565.     this.endTime_ = 1;
  566.  
  567.     this.graph_ = null;
  568.  
  569.     // Horizontal scale factor, in terms of milliseconds per pixel.
  570.     this.scale_ = 1000;
  571.  
  572.     // Initialize the scrollbar.
  573.     this.updateScrollbarRange_(true);
  574.   }
  575.  
  576.   TimelineGraphView.prototype = {
  577.     setScale: function(scale) {
  578.       this.scale_ = scale;
  579.     },
  580.  
  581.     // Returns the total length of the graph, in pixels.
  582.     getLength_: function() {
  583.       var timeRange = this.endTime_ - this.startTime_;
  584.       // Math.floor is used to ignore the last partial area, of length less
  585.       // than this.scale_.
  586.       return Math.floor(timeRange / this.scale_);
  587.     },
  588.  
  589.     /**
  590.      * Returns true if the graph is scrolled all the way to the right.
  591.      */
  592.     graphScrolledToRightEdge_: function() {
  593.       return this.scrollbar_.position_ == this.scrollbar_.range_;
  594.     },
  595.  
  596.     /**
  597.      * Update the range of the scrollbar.  If |resetPosition| is true, also
  598.      * sets the slider to point at the rightmost position and triggers a
  599.      * repaint.
  600.      */
  601.     updateScrollbarRange_: function(resetPosition) {
  602.       var scrollbarRange = this.getLength_() - this.canvas_.width;
  603.       if (scrollbarRange < 0)
  604.         scrollbarRange = 0;
  605.  
  606.       // If we've decreased the range to less than the current scroll position,
  607.       // we need to move the scroll position.
  608.       if (this.scrollbar_.position_ > scrollbarRange)
  609.         resetPosition = true;
  610.  
  611.       this.scrollbar_.range_ = scrollbarRange;
  612.       if (resetPosition) {
  613.         this.scrollbar_.position_ = scrollbarRange;
  614.         this.repaint();
  615.       }
  616.     },
  617.  
  618.     /**
  619.      * Sets the date range displayed on the graph, switches to the default
  620.      * scale factor, and moves the scrollbar all the way to the right.
  621.      */
  622.     setDateRange: function(startDate, endDate) {
  623.       this.startTime_ = startDate.getTime();
  624.       this.endTime_ = endDate.getTime();
  625.  
  626.       // Safety check.
  627.       if (this.endTime_ <= this.startTime_)
  628.         this.startTime_ = this.endTime_ - 1;
  629.  
  630.       this.updateScrollbarRange_(true);
  631.     },
  632.  
  633.     /**
  634.      * Updates the end time at the right of the graph to be the current time.
  635.      * Specifically, updates the scrollbar's range, and if the scrollbar is
  636.      * all the way to the right, keeps it all the way to the right.  Otherwise,
  637.      * leaves the view as-is and doesn't redraw anything.
  638.      */
  639.     updateEndDate: function(opt_date) {
  640.       this.endTime_ = opt_date || (new Date()).getTime();
  641.       this.updateScrollbarRange_(this.graphScrolledToRightEdge_());
  642.     },
  643.  
  644.     getStartDate: function() {
  645.       return new Date(this.startTime_);
  646.     },
  647.  
  648.     /**
  649.      * Replaces the current TimelineDataSeries with |dataSeries|.
  650.      */
  651.     setDataSeries: function(dataSeries) {
  652.       // Simply recreates the Graph.
  653.       this.graph_ = new Graph();
  654.       for (var i = 0; i < dataSeries.length; ++i)
  655.         this.graph_.addDataSeries(dataSeries[i]);
  656.       this.repaint();
  657.     },
  658.  
  659.     /**
  660.     * Adds |dataSeries| to the current graph.
  661.     */
  662.     addDataSeries: function(dataSeries) {
  663.       if (!this.graph_)
  664.         this.graph_ = new Graph();
  665.       this.graph_.addDataSeries(dataSeries);
  666.       this.repaint();
  667.     },
  668.  
  669.     /**
  670.      * Draws the graph on |canvas_|.
  671.      */
  672.     repaint: function() {
  673.       this.repaintTimerRunning_ = false;
  674.  
  675.       var width = this.canvas_.width;
  676.       var height = this.canvas_.height;
  677.       var context = this.canvas_.getContext('2d');
  678.  
  679.       // Clear the canvas.
  680.       context.fillStyle = BACKGROUND_COLOR;
  681.       context.fillRect(0, 0, width, height);
  682.  
  683.       // Try to get font height in pixels.  Needed for layout.
  684.       var fontHeightString = context.font.match(/([0-9]+)px/)[1];
  685.       var fontHeight = parseInt(fontHeightString);
  686.  
  687.       // Safety check, to avoid drawing anything too ugly.
  688.       if (fontHeightString.length == 0 || fontHeight <= 0 ||
  689.           fontHeight * 4 > height || width < 50) {
  690.         return;
  691.       }
  692.  
  693.       // Save current transformation matrix so we can restore it later.
  694.       context.save();
  695.  
  696.       // The center of an HTML canvas pixel is technically at (0.5, 0.5).  This
  697.       // makes near straight lines look bad, due to anti-aliasing.  This
  698.       // translation reduces the problem a little.
  699.       context.translate(0.5, 0.5);
  700.  
  701.       // Figure out what time values to display.
  702.       var position = this.scrollbar_.position_;
  703.       // If the entire time range is being displayed, align the right edge of
  704.       // the graph to the end of the time range.
  705.       if (this.scrollbar_.range_ == 0)
  706.         position = this.getLength_() - this.canvas_.width;
  707.       var visibleStartTime = this.startTime_ + position * this.scale_;
  708.  
  709.       // Make space at the bottom of the graph for the time labels, and then
  710.       // draw the labels.
  711.       var textHeight = height;
  712.       height -= fontHeight + LABEL_VERTICAL_SPACING;
  713.       this.drawTimeLabels(context, width, height, textHeight, visibleStartTime);
  714.  
  715.       // Draw outline of the main graph area.
  716.       context.strokeStyle = GRID_COLOR;
  717.       context.strokeRect(0, 0, width - 1, height - 1);
  718.  
  719.       if (this.graph_) {
  720.         // Layout graph and have them draw their tick marks.
  721.         this.graph_.layout(
  722.             width, height, fontHeight, visibleStartTime, this.scale_);
  723.         this.graph_.drawTicks(context);
  724.  
  725.         // Draw the lines of all graphs, and then draw their labels.
  726.         this.graph_.drawLines(context);
  727.         this.graph_.drawLabels(context);
  728.       }
  729.  
  730.       // Restore original transformation matrix.
  731.       context.restore();
  732.     },
  733.  
  734.     /**
  735.      * Draw time labels below the graph.  Takes in start time as an argument
  736.      * since it may not be |startTime_|, when we're displaying the entire
  737.      * time range.
  738.      */
  739.     drawTimeLabels: function(context, width, height, textHeight, startTime) {
  740.       // Draw the labels 1 minute apart.
  741.       var timeStep = 1000 * 60;
  742.  
  743.       // Find the time for the first label.  This time is a perfect multiple of
  744.       // timeStep because of how UTC times work.
  745.       var time = Math.ceil(startTime / timeStep) * timeStep;
  746.  
  747.       context.textBaseline = 'bottom';
  748.       context.textAlign = 'center';
  749.       context.fillStyle = TEXT_COLOR;
  750.       context.strokeStyle = GRID_COLOR;
  751.  
  752.       // Draw labels and vertical grid lines.
  753.       while (true) {
  754.         var x = Math.round((time - startTime) / this.scale_);
  755.         if (x >= width)
  756.           break;
  757.         var text = (new Date(time)).toLocaleTimeString();
  758.         context.fillText(text, x, textHeight);
  759.         context.beginPath();
  760.         context.lineTo(x, 0);
  761.         context.lineTo(x, height);
  762.         context.stroke();
  763.         time += timeStep;
  764.       }
  765.     },
  766.  
  767.     getDataSeriesCount: function() {
  768.       if (this.graph_)
  769.         return this.graph_.dataSeries_.length;
  770.       return 0;
  771.     },
  772.  
  773.     hasDataSeries: function(dataSeries) {
  774.       if (this.graph_)
  775.         return this.graph_.hasDataSeries(dataSeries);
  776.       return false;
  777.     },
  778.  
  779.   };
  780.  
  781.   /**
  782.    * A Graph is responsible for drawing all the TimelineDataSeries that have
  783.    * the same data type.  Graphs are responsible for scaling the values, laying
  784.    * out labels, and drawing both labels and lines for its data series.
  785.    */
  786.   var Graph = (function() {
  787.     /**
  788.      * @constructor
  789.      */
  790.     function Graph() {
  791.       this.dataSeries_ = [];
  792.  
  793.       // Cached properties of the graph, set in layout.
  794.       this.width_ = 0;
  795.       this.height_ = 0;
  796.       this.fontHeight_ = 0;
  797.       this.startTime_ = 0;
  798.       this.scale_ = 0;
  799.  
  800.       // The lowest/highest values adjusted by the vertical label step size
  801.       // in the displayed range of the graph. Used for scaling and setting
  802.       // labels.  Set in layoutLabels.
  803.       this.min_ = 0;
  804.       this.max_ = 0;
  805.  
  806.       // Cached text of equally spaced labels.  Set in layoutLabels.
  807.       this.labels_ = [];
  808.     }
  809.  
  810.     /**
  811.      * A Label is the label at a particular position along the y-axis.
  812.      * @constructor
  813.      */
  814.     function Label(height, text) {
  815.       this.height = height;
  816.       this.text = text;
  817.     }
  818.  
  819.     Graph.prototype = {
  820.       addDataSeries: function(dataSeries) {
  821.         this.dataSeries_.push(dataSeries);
  822.       },
  823.  
  824.       hasDataSeries: function(dataSeries) {
  825.         for (var i = 0; i < this.dataSeries_.length; ++i) {
  826.           if (this.dataSeries_[i] == dataSeries)
  827.             return true;
  828.         }
  829.         return false;
  830.       },
  831.  
  832.       /**
  833.        * Returns a list of all the values that should be displayed for a given
  834.        * data series, using the current graph layout.
  835.        */
  836.       getValues: function(dataSeries) {
  837.         if (!dataSeries.isVisible())
  838.           return null;
  839.         return dataSeries.getValues(this.startTime_, this.scale_, this.width_);
  840.       },
  841.  
  842.       /**
  843.        * Updates the graph's layout.  In particular, both the max value and
  844.        * label positions are updated.  Must be called before calling any of the
  845.        * drawing functions.
  846.        */
  847.       layout: function(width, height, fontHeight, startTime, scale) {
  848.         this.width_ = width;
  849.         this.height_ = height;
  850.         this.fontHeight_ = fontHeight;
  851.         this.startTime_ = startTime;
  852.         this.scale_ = scale;
  853.  
  854.         // Find largest value.
  855.         var max = 0, min = 0;
  856.         for (var i = 0; i < this.dataSeries_.length; ++i) {
  857.           var values = this.getValues(this.dataSeries_[i]);
  858.           if (!values)
  859.             continue;
  860.           for (var j = 0; j < values.length; ++j) {
  861.             if (values[j] > max)
  862.               max = values[j];
  863.             else if (values[j] < min)
  864.               min = values[j];
  865.           }
  866.         }
  867.  
  868.         this.layoutLabels_(min, max);
  869.       },
  870.  
  871.       /**
  872.        * Lays out labels and sets |max_|/|min_|, taking the time units into
  873.        * consideration.  |maxValue| is the actual maximum value, and
  874.        * |max_| will be set to the value of the largest label, which
  875.        * will be at least |maxValue|. Similar for |min_|.
  876.        */
  877.       layoutLabels_: function(minValue, maxValue) {
  878.         if (maxValue - minValue < 1024) {
  879.           this.layoutLabelsBasic_(minValue, maxValue, MAX_DECIMAL_PRECISION);
  880.           return;
  881.         }
  882.  
  883.         // Find appropriate units to use.
  884.         var units = ['', 'k', 'M', 'G', 'T', 'P'];
  885.         // Units to use for labels.  0 is '1', 1 is K, etc.
  886.         // We start with 1, and work our way up.
  887.         var unit = 1;
  888.         minValue /= 1024;
  889.         maxValue /= 1024;
  890.         while (units[unit + 1] && maxValue - minValue >= 1024) {
  891.           minValue /= 1024;
  892.           maxValue /= 1024;
  893.           ++unit;
  894.         }
  895.  
  896.         // Calculate labels.
  897.         this.layoutLabelsBasic_(minValue, maxValue, MAX_DECIMAL_PRECISION);
  898.  
  899.         // Append units to labels.
  900.         for (var i = 0; i < this.labels_.length; ++i)
  901.           this.labels_[i] += ' ' + units[unit];
  902.  
  903.         // Convert |min_|/|max_| back to unit '1'.
  904.         this.min_ *= Math.pow(1024, unit);
  905.         this.max_ *= Math.pow(1024, unit);
  906.       },
  907.  
  908.       /**
  909.        * Same as layoutLabels_, but ignores units.  |maxDecimalDigits| is the
  910.        * maximum number of decimal digits allowed.  The minimum allowed
  911.        * difference between two adjacent labels is 10^-|maxDecimalDigits|.
  912.        */
  913.       layoutLabelsBasic_: function(minValue, maxValue, maxDecimalDigits) {
  914.         this.labels_ = [];
  915.         var range = maxValue - minValue;
  916.         // No labels if the range is 0.
  917.         if (range == 0) {
  918.           this.min_ = this.max_ = maxValue;
  919.           return;
  920.         }
  921.  
  922.         // The maximum number of equally spaced labels allowed.  |fontHeight_|
  923.         // is doubled because the top two labels are both drawn in the same
  924.         // gap.
  925.         var minLabelSpacing = 2 * this.fontHeight_ + LABEL_VERTICAL_SPACING;
  926.  
  927.         // The + 1 is for the top label.
  928.         var maxLabels = 1 + this.height_ / minLabelSpacing;
  929.         if (maxLabels < 2) {
  930.           maxLabels = 2;
  931.         } else if (maxLabels > MAX_VERTICAL_LABELS) {
  932.           maxLabels = MAX_VERTICAL_LABELS;
  933.         }
  934.  
  935.         // Initial try for step size between conecutive labels.
  936.         var stepSize = Math.pow(10, -maxDecimalDigits);
  937.         // Number of digits to the right of the decimal of |stepSize|.
  938.         // Used for formating label strings.
  939.         var stepSizeDecimalDigits = maxDecimalDigits;
  940.  
  941.         // Pick a reasonable step size.
  942.         while (true) {
  943.           // If we use a step size of |stepSize| between labels, we'll need:
  944.           //
  945.           // Math.ceil(range / stepSize) + 1
  946.           //
  947.           // labels.  The + 1 is because we need labels at both at 0 and at
  948.           // the top of the graph.
  949.  
  950.           // Check if we can use steps of size |stepSize|.
  951.           if (Math.ceil(range / stepSize) + 1 <= maxLabels)
  952.             break;
  953.           // Check |stepSize| * 2.
  954.           if (Math.ceil(range / (stepSize * 2)) + 1 <= maxLabels) {
  955.             stepSize *= 2;
  956.             break;
  957.           }
  958.           // Check |stepSize| * 5.
  959.           if (Math.ceil(range / (stepSize * 5)) + 1 <= maxLabels) {
  960.             stepSize *= 5;
  961.             break;
  962.           }
  963.           stepSize *= 10;
  964.           if (stepSizeDecimalDigits > 0)
  965.             --stepSizeDecimalDigits;
  966.         }
  967.  
  968.         // Set the min/max so it's an exact multiple of the chosen step size.
  969.         this.max_ = Math.ceil(maxValue / stepSize) * stepSize;
  970.         this.min_ = Math.floor(minValue / stepSize) * stepSize;
  971.  
  972.         // Create labels.
  973.         for (var label = this.max_; label >= this.min_; label -= stepSize)
  974.           this.labels_.push(label.toFixed(stepSizeDecimalDigits));
  975.       },
  976.  
  977.       /**
  978.        * Draws tick marks for each of the labels in |labels_|.
  979.        */
  980.       drawTicks: function(context) {
  981.         var x1;
  982.         var x2;
  983.         x1 = this.width_ - 1;
  984.         x2 = this.width_ - 1 - Y_AXIS_TICK_LENGTH;
  985.  
  986.         context.fillStyle = GRID_COLOR;
  987.         context.beginPath();
  988.         for (var i = 1; i < this.labels_.length - 1; ++i) {
  989.           // The rounding is needed to avoid ugly 2-pixel wide anti-aliased
  990.           // lines.
  991.           var y = Math.round(this.height_ * i / (this.labels_.length - 1));
  992.           context.moveTo(x1, y);
  993.           context.lineTo(x2, y);
  994.         }
  995.         context.stroke();
  996.       },
  997.  
  998.       /**
  999.        * Draws a graph line for each of the data series.
  1000.        */
  1001.       drawLines: function(context) {
  1002.         // Factor by which to scale all values to convert them to a number from
  1003.         // 0 to height - 1.
  1004.         var scale = 0;
  1005.         var bottom = this.height_ - 1;
  1006.         if (this.max_)
  1007.           scale = bottom / (this.max_ - this.min_);
  1008.  
  1009.         // Draw in reverse order, so earlier data series are drawn on top of
  1010.         // subsequent ones.
  1011.         for (var i = this.dataSeries_.length - 1; i >= 0; --i) {
  1012.           var values = this.getValues(this.dataSeries_[i]);
  1013.           if (!values)
  1014.             continue;
  1015.           context.strokeStyle = this.dataSeries_[i].getColor();
  1016.           context.beginPath();
  1017.           for (var x = 0; x < values.length; ++x) {
  1018.             // The rounding is needed to avoid ugly 2-pixel wide anti-aliased
  1019.             // horizontal lines.
  1020.             context.lineTo(
  1021.                 x, bottom - Math.round((values[x] - this.min_) * scale));
  1022.           }
  1023.           context.stroke();
  1024.         }
  1025.       },
  1026.  
  1027.       /**
  1028.        * Draw labels in |labels_|.
  1029.        */
  1030.       drawLabels: function(context) {
  1031.         if (this.labels_.length == 0)
  1032.           return;
  1033.         var x = this.width_ - LABEL_HORIZONTAL_SPACING;
  1034.  
  1035.         // Set up the context.
  1036.         context.fillStyle = TEXT_COLOR;
  1037.         context.textAlign = 'right';
  1038.  
  1039.         // Draw top label, which is the only one that appears below its tick
  1040.         // mark.
  1041.         context.textBaseline = 'top';
  1042.         context.fillText(this.labels_[0], x, 0);
  1043.  
  1044.         // Draw all the other labels.
  1045.         context.textBaseline = 'bottom';
  1046.         var step = (this.height_ - 1) / (this.labels_.length - 1);
  1047.         for (var i = 1; i < this.labels_.length; ++i)
  1048.           context.fillText(this.labels_[i], x, step * i);
  1049.       }
  1050.     };
  1051.  
  1052.     return Graph;
  1053.   })();
  1054.  
  1055.   return TimelineGraphView;
  1056. })();
  1057.  
  1058.  
  1059. var STATS_GRAPH_CONTAINER_HEADING_CLASS = 'stats-graph-container-heading';
  1060.  
  1061. var RECEIVED_PROPAGATION_DELTA_LABEL =
  1062.     'googReceivedPacketGroupPropagationDeltaDebug';
  1063. var RECEIVED_PACKET_GROUP_ARRIVAL_TIME_LABEL =
  1064.     'googReceivedPacketGroupArrivalTimeDebug';
  1065.  
  1066. // Specifies which stats should be drawn on the 'bweCompound' graph and how.
  1067. var bweCompoundGraphConfig = {
  1068.   googAvailableSendBandwidth: {color: 'red'},
  1069.   googTargetEncBitrateCorrected: {color: 'purple'},
  1070.   googActualEncBitrate: {color: 'orange'},
  1071.   googRetransmitBitrate: {color: 'blue'},
  1072.   googTransmitBitrate: {color: 'green'},
  1073. };
  1074.  
  1075. // Converts the last entry of |srcDataSeries| from the total amount to the
  1076. // amount per second.
  1077. var totalToPerSecond = function(srcDataSeries) {
  1078.   var length = srcDataSeries.dataPoints_.length;
  1079.   if (length >= 2) {
  1080.     var lastDataPoint = srcDataSeries.dataPoints_[length - 1];
  1081.     var secondLastDataPoint = srcDataSeries.dataPoints_[length - 2];
  1082.     return (lastDataPoint.value - secondLastDataPoint.value) * 1000 /
  1083.            (lastDataPoint.time - secondLastDataPoint.time);
  1084.   }
  1085.  
  1086.   return 0;
  1087. };
  1088.  
  1089. // Converts the value of total bytes to bits per second.
  1090. var totalBytesToBitsPerSecond = function(srcDataSeries) {
  1091.   return totalToPerSecond(srcDataSeries) * 8;
  1092. };
  1093.  
  1094. // Specifies which stats should be converted before drawn and how.
  1095. // |convertedName| is the name of the converted value, |convertFunction|
  1096. // is the function used to calculate the new converted value based on the
  1097. // original dataSeries.
  1098. var dataConversionConfig = {
  1099.   packetsSent: {
  1100.     convertedName: 'packetsSentPerSecond',
  1101.     convertFunction: totalToPerSecond,
  1102.   },
  1103.   bytesSent: {
  1104.     convertedName: 'bitsSentPerSecond',
  1105.     convertFunction: totalBytesToBitsPerSecond,
  1106.   },
  1107.   packetsReceived: {
  1108.     convertedName: 'packetsReceivedPerSecond',
  1109.     convertFunction: totalToPerSecond,
  1110.   },
  1111.   bytesReceived: {
  1112.     convertedName: 'bitsReceivedPerSecond',
  1113.     convertFunction: totalBytesToBitsPerSecond,
  1114.   },
  1115.   // This is due to a bug of wrong units reported for googTargetEncBitrate.
  1116.   // TODO (jiayl): remove this when the unit bug is fixed.
  1117.   googTargetEncBitrate: {
  1118.     convertedName: 'googTargetEncBitrateCorrected',
  1119.     convertFunction: function (srcDataSeries) {
  1120.       var length = srcDataSeries.dataPoints_.length;
  1121.       var lastDataPoint = srcDataSeries.dataPoints_[length - 1];
  1122.       if (lastDataPoint.value < 5000)
  1123.         return lastDataPoint.value * 1000;
  1124.       return lastDataPoint.value;
  1125.     }
  1126.   }
  1127. };
  1128.  
  1129.  
  1130. // The object contains the stats names that should not be added to the graph,
  1131. // even if they are numbers.
  1132. var statsNameBlackList = {
  1133.   'ssrc': true,
  1134.   'googTrackId': true,
  1135.   'googComponent': true,
  1136.   'googLocalAddress': true,
  1137.   'googRemoteAddress': true,
  1138.   'googFingerprint': true,
  1139. };
  1140.  
  1141. var graphViews = {};
  1142.  
  1143. // Returns number parsed from |value|, or NaN if the stats name is black-listed.
  1144. function getNumberFromValue(name, value) {
  1145.   if (statsNameBlackList[name])
  1146.     return NaN;
  1147.   return parseFloat(value);
  1148. }
  1149.  
  1150. // Adds the stats report |report| to the timeline graph for the given
  1151. // |peerConnectionElement|.
  1152. function drawSingleReport(peerConnectionElement, report) {
  1153.   var reportType = report.type;
  1154.   var reportId = report.id;
  1155.   var stats = report.stats;
  1156.   if (!stats || !stats.values)
  1157.     return;
  1158.  
  1159.   for (var i = 0; i < stats.values.length - 1; i = i + 2) {
  1160.     var rawLabel = stats.values[i];
  1161.     // Propagation deltas are handled separately.
  1162.     if (rawLabel == RECEIVED_PROPAGATION_DELTA_LABEL) {
  1163.       drawReceivedPropagationDelta(
  1164.           peerConnectionElement, report, stats.values[i + 1]);
  1165.       continue;
  1166.     }
  1167.     var rawDataSeriesId = reportId + '-' + rawLabel;
  1168.     var rawValue = getNumberFromValue(rawLabel, stats.values[i + 1]);
  1169.     if (isNaN(rawValue)) {
  1170.       // We do not draw non-numerical values, but still want to record it in the
  1171.       // data series.
  1172.       addDataSeriesPoints(peerConnectionElement,
  1173.                           rawDataSeriesId,
  1174.                           rawLabel,
  1175.                           [stats.timestamp],
  1176.                           [stats.values[i + 1]]);
  1177.       continue;
  1178.     }
  1179.  
  1180.     var finalDataSeriesId = rawDataSeriesId;
  1181.     var finalLabel = rawLabel;
  1182.     var finalValue = rawValue;
  1183.     // We need to convert the value if dataConversionConfig[rawLabel] exists.
  1184.     if (dataConversionConfig[rawLabel]) {
  1185.       // Updates the original dataSeries before the conversion.
  1186.       addDataSeriesPoints(peerConnectionElement,
  1187.                           rawDataSeriesId,
  1188.                           rawLabel,
  1189.                           [stats.timestamp],
  1190.                           [rawValue]);
  1191.  
  1192.       // Convert to another value to draw on graph, using the original
  1193.       // dataSeries as input.
  1194.       finalValue = dataConversionConfig[rawLabel].convertFunction(
  1195.           peerConnectionDataStore[peerConnectionElement.id].getDataSeries(
  1196.               rawDataSeriesId));
  1197.       finalLabel = dataConversionConfig[rawLabel].convertedName;
  1198.       finalDataSeriesId = reportId + '-' + finalLabel;
  1199.     }
  1200.  
  1201.     // Updates the final dataSeries to draw.
  1202.     addDataSeriesPoints(peerConnectionElement,
  1203.                         finalDataSeriesId,
  1204.                         finalLabel,
  1205.                         [stats.timestamp],
  1206.                         [finalValue]);
  1207.  
  1208.     // Updates the graph.
  1209.     var graphType = bweCompoundGraphConfig[finalLabel] ?
  1210.                     'bweCompound' : finalLabel;
  1211.     var graphViewId =
  1212.         peerConnectionElement.id + '-' + reportId + '-' + graphType;
  1213.  
  1214.     if (!graphViews[graphViewId]) {
  1215.       graphViews[graphViewId] = createStatsGraphView(peerConnectionElement,
  1216.                                                      report,
  1217.                                                      graphType);
  1218.       var date = new Date(stats.timestamp);
  1219.       graphViews[graphViewId].setDateRange(date, date);
  1220.     }
  1221.     // Adds the new dataSeries to the graphView. We have to do it here to cover
  1222.     // both the simple and compound graph cases.
  1223.     var dataSeries =
  1224.         peerConnectionDataStore[peerConnectionElement.id].getDataSeries(
  1225.             finalDataSeriesId);
  1226.     if (!graphViews[graphViewId].hasDataSeries(dataSeries))
  1227.       graphViews[graphViewId].addDataSeries(dataSeries);
  1228.     graphViews[graphViewId].updateEndDate();
  1229.   }
  1230. }
  1231.  
  1232. // Makes sure the TimelineDataSeries with id |dataSeriesId| is created,
  1233. // and adds the new data points to it. |times| is the list of timestamps for
  1234. // each data point, and |values| is the list of the data point values.
  1235. function addDataSeriesPoints(
  1236.     peerConnectionElement, dataSeriesId, label, times, values) {
  1237.   var dataSeries =
  1238.     peerConnectionDataStore[peerConnectionElement.id].getDataSeries(
  1239.         dataSeriesId);
  1240.   if (!dataSeries) {
  1241.     dataSeries = new TimelineDataSeries();
  1242.     peerConnectionDataStore[peerConnectionElement.id].setDataSeries(
  1243.         dataSeriesId, dataSeries);
  1244.     if (bweCompoundGraphConfig[label]) {
  1245.       dataSeries.setColor(bweCompoundGraphConfig[label].color);
  1246.     }
  1247.   }
  1248.   for (var i = 0; i < times.length; ++i)
  1249.     dataSeries.addPoint(times[i], values[i]);
  1250. }
  1251.  
  1252. // Draws the received propagation deltas using the packet group arrival time as
  1253. // the x-axis. For example, |report.stats.values| should be like
  1254. // ['googReceivedPacketGroupArrivalTimeDebug', '[123456, 234455, 344566]',
  1255. //  'googReceivedPacketGroupPropagationDeltaDebug', '[23, 45, 56]', ...].
  1256. function drawReceivedPropagationDelta(peerConnectionElement, report, deltas) {
  1257.   var reportId = report.id;
  1258.   var stats = report.stats;
  1259.   var times = null;
  1260.   // Find the packet group arrival times.
  1261.   for (var i = 0; i < stats.values.length - 1; i = i + 2) {
  1262.     if (stats.values[i] == RECEIVED_PACKET_GROUP_ARRIVAL_TIME_LABEL) {
  1263.       times = stats.values[i + 1];
  1264.       break;
  1265.     }
  1266.   }
  1267.   // Unexpected.
  1268.   if (times == null)
  1269.     return;
  1270.  
  1271.   // Convert |deltas| and |times| from strings to arrays of numbers.
  1272.   try {
  1273.     deltas = JSON.parse(deltas);
  1274.     times = JSON.parse(times);
  1275.   } catch (e) {
  1276.     console.log(e);
  1277.     return;
  1278.   }
  1279.  
  1280.   // Update the data series.
  1281.   var dataSeriesId = reportId + '-' + RECEIVED_PROPAGATION_DELTA_LABEL;
  1282.   addDataSeriesPoints(
  1283.       peerConnectionElement,
  1284.       dataSeriesId,
  1285.       RECEIVED_PROPAGATION_DELTA_LABEL,
  1286.       times,
  1287.       deltas);
  1288.   // Update the graph.
  1289.   var graphViewId = peerConnectionElement.id + '-' + reportId + '-' +
  1290.       RECEIVED_PROPAGATION_DELTA_LABEL;
  1291.   var date = new Date(times[times.length - 1]);
  1292.   if (!graphViews[graphViewId]) {
  1293.     graphViews[graphViewId] = createStatsGraphView(
  1294.         peerConnectionElement,
  1295.         report,
  1296.         RECEIVED_PROPAGATION_DELTA_LABEL);
  1297.     graphViews[graphViewId].setScale(10);
  1298.     graphViews[graphViewId].setDateRange(date, date);
  1299.     var dataSeries = peerConnectionDataStore[peerConnectionElement.id]
  1300.         .getDataSeries(dataSeriesId);
  1301.     graphViews[graphViewId].addDataSeries(dataSeries);
  1302.   }
  1303.   graphViews[graphViewId].updateEndDate(date);
  1304. }
  1305.  
  1306. // Ensures a div container to hold all stats graphs for one track is created as
  1307. // a child of |peerConnectionElement|.
  1308. function ensureStatsGraphTopContainer(peerConnectionElement, report) {
  1309.   var containerId = peerConnectionElement.id + '-' +
  1310.       report.type + '-' + report.id + '-graph-container';
  1311.   var container = $(containerId);
  1312.   if (!container) {
  1313.     container = document.createElement('details');
  1314.     container.id = containerId;
  1315.     container.className = 'stats-graph-container';
  1316.  
  1317.     peerConnectionElement.appendChild(container);
  1318.     container.innerHTML ='<summary><span></span></summary>';
  1319.     container.firstChild.firstChild.className =
  1320.         STATS_GRAPH_CONTAINER_HEADING_CLASS;
  1321.     container.firstChild.firstChild.textContent =
  1322.         'Stats graphs for ' + report.id;
  1323.  
  1324.     if (report.type == 'ssrc') {
  1325.       var ssrcInfoElement = document.createElement('div');
  1326.       container.firstChild.appendChild(ssrcInfoElement);
  1327.       ssrcInfoManager.populateSsrcInfo(ssrcInfoElement,
  1328.                                        GetSsrcFromReport(report));
  1329.     }
  1330.   }
  1331.   return container;
  1332. }
  1333.  
  1334. // Creates the container elements holding a timeline graph
  1335. // and the TimelineGraphView object.
  1336. function createStatsGraphView(
  1337.     peerConnectionElement, report, statsName) {
  1338.   var topContainer = ensureStatsGraphTopContainer(peerConnectionElement,
  1339.                                                   report);
  1340.  
  1341.   var graphViewId =
  1342.       peerConnectionElement.id + '-' + report.id + '-' + statsName;
  1343.   var divId = graphViewId + '-div';
  1344.   var canvasId = graphViewId + '-canvas';
  1345.   var container = document.createElement("div");
  1346.   container.className = 'stats-graph-sub-container';
  1347.  
  1348.   topContainer.appendChild(container);
  1349.   container.innerHTML = '<div>' + statsName + '</div>' +
  1350.       '<div id=' + divId + '><canvas id=' + canvasId + '></canvas></div>';
  1351.   if (statsName == 'bweCompound') {
  1352.       container.insertBefore(
  1353.           createBweCompoundLegend(peerConnectionElement, report.id),
  1354.           $(divId));
  1355.   }
  1356.   return new TimelineGraphView(divId, canvasId);
  1357. }
  1358.  
  1359. // Creates the legend section for the bweCompound graph.
  1360. // Returns the legend element.
  1361. function createBweCompoundLegend(peerConnectionElement, reportId) {
  1362.   var legend = document.createElement('div');
  1363.   for (var prop in bweCompoundGraphConfig) {
  1364.     var div = document.createElement('div');
  1365.     legend.appendChild(div);
  1366.     div.innerHTML = '<input type=checkbox checked></input>' + prop;
  1367.     div.style.color = bweCompoundGraphConfig[prop].color;
  1368.     div.dataSeriesId = reportId + '-' + prop;
  1369.     div.graphViewId =
  1370.         peerConnectionElement.id + '-' + reportId + '-bweCompound';
  1371.     div.firstChild.addEventListener('click', function(event) {
  1372.         var target =
  1373.             peerConnectionDataStore[peerConnectionElement.id].getDataSeries(
  1374.                 event.target.parentNode.dataSeriesId);
  1375.         target.show(event.target.checked);
  1376.         graphViews[event.target.parentNode.graphViewId].repaint();
  1377.     });
  1378.   }
  1379.   return legend;
  1380. }
  1381.  
  1382. // Copyright (c) 2013 The Chromium Authors. All rights reserved.
  1383. // Use of this source code is governed by a BSD-style license that can be
  1384. // found in the LICENSE file.
  1385.  
  1386.  
  1387. /**
  1388.  * Maintains the stats table.
  1389.  * @param {SsrcInfoManager} ssrcInfoManager The source of the ssrc info.
  1390.  */
  1391. var StatsTable = (function(ssrcInfoManager) {
  1392.   'use strict';
  1393.  
  1394.   /**
  1395.    * @param {SsrcInfoManager} ssrcInfoManager The source of the ssrc info.
  1396.    * @constructor
  1397.    */
  1398.   function StatsTable(ssrcInfoManager) {
  1399.     /**
  1400.      * @type {SsrcInfoManager}
  1401.      * @private
  1402.      */
  1403.     this.ssrcInfoManager_ = ssrcInfoManager;
  1404.   }
  1405.  
  1406.   StatsTable.prototype = {
  1407.     /**
  1408.      * Adds |report| to the stats table of |peerConnectionElement|.
  1409.      *
  1410.      * @param {!Element} peerConnectionElement The root element.
  1411.      * @param {!Object} report The object containing stats, which is the object
  1412.      *     containing timestamp and values, which is an array of strings, whose
  1413.      *     even index entry is the name of the stat, and the odd index entry is
  1414.      *     the value.
  1415.      */
  1416.     addStatsReport: function(peerConnectionElement, report) {
  1417.       var statsTable = this.ensureStatsTable_(peerConnectionElement, report);
  1418.  
  1419.       if (report.stats) {
  1420.         this.addStatsToTable_(statsTable,
  1421.                               report.stats.timestamp, report.stats.values);
  1422.       }
  1423.     },
  1424.  
  1425.     /**
  1426.      * Ensure the DIV container for the stats tables is created as a child of
  1427.      * |peerConnectionElement|.
  1428.      *
  1429.      * @param {!Element} peerConnectionElement The root element.
  1430.      * @return {!Element} The stats table container.
  1431.      * @private
  1432.      */
  1433.     ensureStatsTableContainer_: function(peerConnectionElement) {
  1434.       var containerId = peerConnectionElement.id + '-table-container';
  1435.       var container = $(containerId);
  1436.       if (!container) {
  1437.         container = document.createElement('div');
  1438.         container.id = containerId;
  1439.         container.className = 'stats-table-container';
  1440.         var head = document.createElement('div');
  1441.         head.textContent = 'Stats Tables';
  1442.         container.appendChild(head);
  1443.         peerConnectionElement.appendChild(container);
  1444.       }
  1445.       return container;
  1446.     },
  1447.  
  1448.     /**
  1449.      * Ensure the stats table for track specified by |report| of PeerConnection
  1450.      * |peerConnectionElement| is created.
  1451.      *
  1452.      * @param {!Element} peerConnectionElement The root element.
  1453.      * @param {!Object} report The object containing stats, which is the object
  1454.      *     containing timestamp and values, which is an array of strings, whose
  1455.      *     even index entry is the name of the stat, and the odd index entry is
  1456.      *     the value.
  1457.      * @return {!Element} The stats table element.
  1458.      * @private
  1459.      */
  1460.      ensureStatsTable_: function(peerConnectionElement, report) {
  1461.       var tableId = peerConnectionElement.id + '-table-' + report.id;
  1462.       var table = $(tableId);
  1463.       if (!table) {
  1464.         var container = this.ensureStatsTableContainer_(peerConnectionElement);
  1465.         var details = document.createElement('details');
  1466.         container.appendChild(details);
  1467.  
  1468.         var summary = document.createElement('summary');
  1469.         summary.textContent = report.id;
  1470.         details.appendChild(summary);
  1471.  
  1472.         table = document.createElement('table');
  1473.         details.appendChild(table);
  1474.         table.id = tableId;
  1475.         table.border = 1;
  1476.  
  1477.         table.innerHTML = '<tr><th colspan=2></th></tr>';
  1478.         table.rows[0].cells[0].textContent = 'Statistics ' + report.id;
  1479.         if (report.type == 'ssrc') {
  1480.             table.insertRow(1);
  1481.             table.rows[1].innerHTML = '<td colspan=2></td>';
  1482.             this.ssrcInfoManager_.populateSsrcInfo(
  1483.                 table.rows[1].cells[0], GetSsrcFromReport(report));
  1484.         }
  1485.       }
  1486.       return table;
  1487.     },
  1488.  
  1489.     /**
  1490.      * Update |statsTable| with |time| and |statsData|.
  1491.      *
  1492.      * @param {!Element} statsTable Which table to update.
  1493.      * @param {number} time The number of miliseconds since epoch.
  1494.      * @param {Array.<string>} statsData An array of stats name and value pairs.
  1495.      * @private
  1496.      */
  1497.     addStatsToTable_: function(statsTable, time, statsData) {
  1498.       var date = Date(time);
  1499.       this.updateStatsTableRow_(statsTable, 'timestamp', date.toLocaleString());
  1500.       for (var i = 0; i < statsData.length - 1; i = i + 2) {
  1501.         this.updateStatsTableRow_(statsTable, statsData[i], statsData[i + 1]);
  1502.       }
  1503.     },
  1504.  
  1505.     /**
  1506.      * Update the value column of the stats row of |rowName| to |value|.
  1507.      * A new row is created is this is the first report of this stats.
  1508.      *
  1509.      * @param {!Element} statsTable Which table to update.
  1510.      * @param {string} rowName The name of the row to update.
  1511.      * @param {string} value The new value to set.
  1512.      * @private
  1513.      */
  1514.     updateStatsTableRow_: function(statsTable, rowName, value) {
  1515.       var trId = statsTable.id + '-' + rowName;
  1516.       var trElement = $(trId);
  1517.       if (!trElement) {
  1518.         trElement = document.createElement('tr');
  1519.         trElement.id = trId;
  1520.         statsTable.firstChild.appendChild(trElement);
  1521.         trElement.innerHTML = '<td>' + rowName + '</td><td></td>';
  1522.       }
  1523.       trElement.cells[1].textContent = value;
  1524.  
  1525.       // Highlights the table for the active connection.
  1526.       if (rowName == 'googActiveConnection' && value == 'true')
  1527.         statsTable.parentElement.classList.add('stats-table-active-connection');
  1528.     }
  1529.   };
  1530.  
  1531.   return StatsTable;
  1532. })();
  1533.  
  1534. // Copyright (c) 2013 The Chromium Authors. All rights reserved.
  1535. // Use of this source code is governed by a BSD-style license that can be
  1536. // found in the LICENSE file.
  1537.  
  1538.  
  1539. /**
  1540.  * The data of a peer connection update.
  1541.  * @param {number} pid The id of the renderer.
  1542.  * @param {number} lid The id of the peer conneciton inside a renderer.
  1543.  * @param {string} type The type of the update.
  1544.  * @param {string} value The details of the update.
  1545.  * @constructor
  1546.  */
  1547. var PeerConnectionUpdateEntry = function(pid, lid, type, value) {
  1548.   /**
  1549.    * @type {number}
  1550.    */
  1551.   this.pid = pid;
  1552.  
  1553.   /**
  1554.    * @type {number}
  1555.    */
  1556.   this.lid = lid;
  1557.  
  1558.   /**
  1559.    * @type {string}
  1560.    */
  1561.   this.type = type;
  1562.  
  1563.   /**
  1564.    * @type {string}
  1565.    */
  1566.   this.value = value;
  1567. };
  1568.  
  1569.  
  1570. /**
  1571.  * Maintains the peer connection update log table.
  1572.  */
  1573. var PeerConnectionUpdateTable = (function() {
  1574.   'use strict';
  1575.  
  1576.   /**
  1577.    * @constructor
  1578.    */
  1579.   function PeerConnectionUpdateTable() {
  1580.     /**
  1581.      * @type {string}
  1582.      * @const
  1583.      * @private
  1584.      */
  1585.     this.UPDATE_LOG_ID_SUFFIX_ = '-update-log';
  1586.  
  1587.     /**
  1588.      * @type {string}
  1589.      * @const
  1590.      * @private
  1591.      */
  1592.     this.UPDATE_LOG_CONTAINER_CLASS_ = 'update-log-container';
  1593.  
  1594.     /**
  1595.      * @type {string}
  1596.      * @const
  1597.      * @private
  1598.      */
  1599.     this.UPDATE_LOG_TABLE_CLASS = 'update-log-table';
  1600.   }
  1601.  
  1602.   PeerConnectionUpdateTable.prototype = {
  1603.     /**
  1604.      * Adds the update to the update table as a new row. The type of the update
  1605.      * is set to the summary of the cell; clicking the cell will reveal or hide
  1606.      * the details as the content of a TextArea element.
  1607.      *
  1608.      * @param {!Element} peerConnectionElement The root element.
  1609.      * @param {!PeerConnectionUpdateEntry} update The update to add.
  1610.      */
  1611.     addPeerConnectionUpdate: function(peerConnectionElement, update) {
  1612.       var tableElement = this.ensureUpdateContainer_(peerConnectionElement);
  1613.  
  1614.       var row = document.createElement('tr');
  1615.       tableElement.firstChild.appendChild(row);
  1616.  
  1617.       row.innerHTML = '<td>' + (new Date()).toLocaleString() + '</td>';
  1618.  
  1619.       if (update.value.length == 0) {
  1620.         row.innerHTML += '<td>' + update.type + '</td>';
  1621.         return;
  1622.       }
  1623.  
  1624.       row.innerHTML += '<td><details><summary>' + update.type +
  1625.           '</summary></details></td>';
  1626.  
  1627.       var valueContainer = document.createElement('pre');
  1628.       var details = row.cells[1].childNodes[0];
  1629.       details.appendChild(valueContainer);
  1630.       valueContainer.textContent = update.value;
  1631.     },
  1632.  
  1633.     /**
  1634.      * Makes sure the update log table of the peer connection is created.
  1635.      *
  1636.      * @param {!Element} peerConnectionElement The root element.
  1637.      * @return {!Element} The log table element.
  1638.      * @private
  1639.      */
  1640.     ensureUpdateContainer_: function(peerConnectionElement) {
  1641.       var tableId = peerConnectionElement.id + this.UPDATE_LOG_ID_SUFFIX_;
  1642.       var tableElement = $(tableId);
  1643.       if (!tableElement) {
  1644.         var tableContainer = document.createElement('div');
  1645.         tableContainer.className = this.UPDATE_LOG_CONTAINER_CLASS_;
  1646.         peerConnectionElement.appendChild(tableContainer);
  1647.  
  1648.         tableElement = document.createElement('table');
  1649.         tableElement.className = this.UPDATE_LOG_TABLE_CLASS;
  1650.         tableElement.id = tableId;
  1651.         tableElement.border = 1;
  1652.         tableContainer.appendChild(tableElement);
  1653.         tableElement.innerHTML = '<tr><th>Time</th>' +
  1654.             '<th class="update-log-header-event">Event</th></tr>';
  1655.       }
  1656.       return tableElement;
  1657.     }
  1658.   };
  1659.  
  1660.   return PeerConnectionUpdateTable;
  1661. })();
  1662.  
  1663. // Copyright (c) 2013 The Chromium Authors. All rights reserved.
  1664. // Use of this source code is governed by a BSD-style license that can be
  1665. // found in the LICENSE file.
  1666.  
  1667.  
  1668. /**
  1669.  * Provides the UI for dump creation.
  1670.  */
  1671. var DumpCreator = (function() {
  1672.   /**
  1673.    * @param {Element} containerElement The parent element of the dump creation
  1674.    *     UI.
  1675.    * @constructor
  1676.    */
  1677.   function DumpCreator(containerElement) {
  1678.     /**
  1679.      * The root element of the dump creation UI.
  1680.      * @type {Element}
  1681.      * @private
  1682.      */
  1683.     this.root_ = document.createElement('details');
  1684.  
  1685.     this.root_.className = 'peer-connection-dump-root';
  1686.     containerElement.appendChild(this.root_);
  1687.     var summary = document.createElement('summary');
  1688.     this.root_.appendChild(summary);
  1689.     summary.textContent = 'Create Dump';
  1690.     var content = document.createElement('div');
  1691.     this.root_.appendChild(content);
  1692.  
  1693.     content.innerHTML = '<div><a><button>' +
  1694.         'Download the PeerConnection updates and stats data' +
  1695.         '</button></a></div>' +
  1696.         '<p><label><input type=checkbox>' +
  1697.         'Enable diagnostic audio recordings.</label></p>' +
  1698.         '<p>A diagnostic audio recording is used for analyzing audio' +
  1699.         ' problems. It contains the audio played out from the speaker and' +
  1700.         ' recorded from the microphone and is saved to the local disk.' +
  1701.         ' Checking this box will enable the recording for an ongoing WebRTC' +
  1702.         ' call and for future WebRTC calls. When the box is unchecked or this' +
  1703.         ' page is closed, this recording functionality will be disabled for' +
  1704.         ' future WebRTC calls, but an ongoing call will continue to record' +
  1705.         ' until the call is ended. Only recording in one tab is supported.' +
  1706.         ' If several tabs are running WebRTC calls, the resulting file will' +
  1707.         ' be invalid. To restart the dump, the tab with the call being' +
  1708.         ' recorded must be closed and recording disabled and enabled again.' +
  1709.         ' When enabling, you select a file to save the dump to. Choose a' +
  1710.         ' non-existing file name. Selecting an existing file will append to' +
  1711.         ' it, not overwrite it, rendering the file invalid. </p>';
  1712.  
  1713.     content.getElementsByTagName('a')[0].addEventListener(
  1714.         'click', this.onDownloadData_.bind(this));
  1715.     content.getElementsByTagName('input')[0].addEventListener(
  1716.         'click', this.onAecRecordingChanged_.bind(this));
  1717.   }
  1718.  
  1719.   DumpCreator.prototype = {
  1720.     // Mark the AEC recording checkbox checked.
  1721.     enableAecRecording: function() {
  1722.       this.root_.getElementsByTagName('input')[0].checked = true;
  1723.     },
  1724.  
  1725.     // Mark the AEC recording checkbox unchecked.
  1726.     disableAecRecording: function() {
  1727.       this.root_.getElementsByTagName('input')[0].checked = false;
  1728.     },
  1729.  
  1730.     /**
  1731.      * Downloads the PeerConnection updates and stats data as a file.
  1732.      *
  1733.      * @private
  1734.      */
  1735.     onDownloadData_: function() {
  1736.       var dump_object =
  1737.       {
  1738.         'getUserMedia': userMediaRequests,
  1739.         'PeerConnections': peerConnectionDataStore,
  1740.       };
  1741.       var textBlob = new Blob([JSON.stringify(dump_object, null, ' ')],
  1742.                               {type: 'octet/stream'});
  1743.       var URL = window.URL.createObjectURL(textBlob);
  1744.  
  1745.       this.root_.getElementsByTagName('a')[0].href = URL;
  1746.       // The default action of the anchor will download the URL.
  1747.     },
  1748.  
  1749.     /**
  1750.      * Handles the event of toggling the AEC recording state.
  1751.      *
  1752.      * @private
  1753.      */
  1754.     onAecRecordingChanged_: function() {
  1755.       var enabled = this.root_.getElementsByTagName('input')[0].checked;
  1756.       if (enabled) {
  1757.         chrome.send('enableAecRecording');
  1758.       } else {
  1759.         chrome.send('disableAecRecording');
  1760.       }
  1761.     },
  1762.   };
  1763.   return DumpCreator;
  1764. })();
  1765.  
  1766.  
  1767.  
  1768. function initialize() {
  1769.   dumpCreator = new DumpCreator($('content-root'));
  1770.   tabView = new TabView($('content-root'));
  1771.   ssrcInfoManager = new SsrcInfoManager();
  1772.   peerConnectionUpdateTable = new PeerConnectionUpdateTable();
  1773.   statsTable = new StatsTable(ssrcInfoManager);
  1774.  
  1775.   chrome.send('finishedDOMLoad');
  1776.  
  1777.   // Requests stats from all peer connections every second.
  1778.   window.setInterval(requestStats, 1000);
  1779. }
  1780. document.addEventListener('DOMContentLoaded', initialize);
  1781.  
  1782.  
  1783. /** Sends a request to the browser to get peer connection statistics. */
  1784. function requestStats() {
  1785.   if (Object.keys(peerConnectionDataStore).length > 0)
  1786.     chrome.send('getAllStats');
  1787. }
  1788.  
  1789.  
  1790. /**
  1791.  * A helper function for getting a peer connection element id.
  1792.  *
  1793.  * @param {!Object.<string, number>} data The object containing the pid and lid
  1794.  *     of the peer connection.
  1795.  * @return {string} The peer connection element id.
  1796.  */
  1797. function getPeerConnectionId(data) {
  1798.   return data.pid + '-' + data.lid;
  1799. }
  1800.  
  1801.  
  1802. /**
  1803.  * Extracts ssrc info from a setLocal/setRemoteDescription update.
  1804.  *
  1805.  * @param {!PeerConnectionUpdateEntry} data The peer connection update data.
  1806.  */
  1807. function extractSsrcInfo(data) {
  1808.   if (data.type == 'setLocalDescription' ||
  1809.       data.type == 'setRemoteDescription') {
  1810.     ssrcInfoManager.addSsrcStreamInfo(data.value);
  1811.   }
  1812. }
  1813.  
  1814.  
  1815. /**
  1816.  * Helper for adding a peer connection update.
  1817.  *
  1818.  * @param {Element} peerConnectionElement
  1819.  * @param {!PeerConnectionUpdateEntry} update The peer connection update data.
  1820.  */
  1821. function addPeerConnectionUpdate(peerConnectionElement, update) {
  1822.   peerConnectionUpdateTable.addPeerConnectionUpdate(peerConnectionElement,
  1823.                                                     update);
  1824.   extractSsrcInfo(update);
  1825.   peerConnectionDataStore[peerConnectionElement.id].addUpdate(
  1826.       update.type, update.value);
  1827. }
  1828.  
  1829.  
  1830. /** Browser message handlers. */
  1831.  
  1832.  
  1833. /**
  1834.  * Removes all information about a peer connection.
  1835.  *
  1836.  * @param {!Object.<string, number>} data The object containing the pid and lid
  1837.  *     of a peer connection.
  1838.  */
  1839. function removePeerConnection(data) {
  1840.   var element = $(getPeerConnectionId(data));
  1841.   if (element) {
  1842.     delete peerConnectionDataStore[element.id];
  1843.     tabView.removeTab(element.id);
  1844.   }
  1845. }
  1846.  
  1847.  
  1848. /**
  1849.  * Adds a peer connection.
  1850.  *
  1851.  * @param {!Object} data The object containing the pid, lid, url, servers, and
  1852.  *     constraints of a peer connection.
  1853.  */
  1854. function addPeerConnection(data) {
  1855.   var id = getPeerConnectionId(data);
  1856.  
  1857.   if (!peerConnectionDataStore[id]) {
  1858.     peerConnectionDataStore[id] = new PeerConnectionRecord();
  1859.   }
  1860.   peerConnectionDataStore[id].initialize(
  1861.       data.url, data.servers, data.constraints);
  1862.  
  1863.   var peerConnectionElement = $(id);
  1864.   if (!peerConnectionElement) {
  1865.     peerConnectionElement = tabView.addTab(id, data.url);
  1866.   }
  1867.   peerConnectionElement.innerHTML =
  1868.       '<p>' + data.url + ' ' + data.servers + ' ' + data.constraints +
  1869.       '</p>';
  1870.  
  1871.   return peerConnectionElement;
  1872. }
  1873.  
  1874.  
  1875. /**
  1876.  * Adds a peer connection update.
  1877.  *
  1878.  * @param {!PeerConnectionUpdateEntry} data The peer connection update data.
  1879.  */
  1880. function updatePeerConnection(data) {
  1881.   var peerConnectionElement = $(getPeerConnectionId(data));
  1882.   addPeerConnectionUpdate(peerConnectionElement, data);
  1883. }
  1884.  
  1885.  
  1886. /**
  1887.  * Adds the information of all peer connections created so far.
  1888.  *
  1889.  * @param {Array.<!Object>} data An array of the information of all peer
  1890.  *     connections. Each array item contains pid, lid, url, servers,
  1891.  *     constraints, and an array of updates as the log.
  1892.  */
  1893. function updateAllPeerConnections(data) {
  1894.   for (var i = 0; i < data.length; ++i) {
  1895.     var peerConnection = addPeerConnection(data[i]);
  1896.  
  1897.     var log = data[i].log;
  1898.     if (!log)
  1899.       continue;
  1900.     for (var j = 0; j < log.length; ++j) {
  1901.       addPeerConnectionUpdate(peerConnection, log[j]);
  1902.     }
  1903.   }
  1904.   requestStats();
  1905. }
  1906.  
  1907.  
  1908. /**
  1909.  * Handles the report of stats.
  1910.  *
  1911.  * @param {!Object} data The object containing pid, lid, and reports, where
  1912.  *     reports is an array of stats reports. Each report contains id, type,
  1913.  *     and stats, where stats is the object containing timestamp and values,
  1914.  *     which is an array of strings, whose even index entry is the name of the
  1915.  *     stat, and the odd index entry is the value.
  1916.  */
  1917. function addStats(data) {
  1918.   var peerConnectionElement = $(getPeerConnectionId(data));
  1919.   if (!peerConnectionElement)
  1920.     return;
  1921.  
  1922.   for (var i = 0; i < data.reports.length; ++i) {
  1923.     var report = data.reports[i];
  1924.     statsTable.addStatsReport(peerConnectionElement, report);
  1925.     drawSingleReport(peerConnectionElement, report);
  1926.   }
  1927. }
  1928.  
  1929.  
  1930. /**
  1931.  * Adds a getUserMedia request.
  1932.  *
  1933.  * @param {!Object} data The object containing rid {number}, pid {number},
  1934.  *     origin {string}, audio {string}, video {string}.
  1935.  */
  1936. function addGetUserMedia(data) {
  1937.   // TODO(jiayl): add the getUserMedia info to the tabbed UI.
  1938.   userMediaRequests.push(data);
  1939. }
  1940.  
  1941.  
  1942. /**
  1943.  * Removes the getUserMedia requests from the specified |rid|.
  1944.  *
  1945.  * @param {!Object} data The object containing rid {number}, the render id.
  1946.  */
  1947. function removeGetUserMediaForRenderer(data) {
  1948.   // TODO(jiayl): remove the getUserMedia info from the tabbed UI.
  1949.   for (var i = userMediaRequests.length - 1; i >= 0; --i) {
  1950.     if (userMediaRequests[i].rid == data.rid)
  1951.       userMediaRequests.splice(i, 1);
  1952.   }
  1953. }
  1954.  
  1955.  
  1956. /**
  1957.  * Notification that the AEC recording file selection dialog was cancelled,
  1958.  * i.e. AEC has not been enabled.
  1959.  */
  1960. function aecRecordingFileSelectionCancelled() {
  1961.   dumpCreator.disableAecRecording();
  1962. }
  1963.  
  1964.  
  1965. /**
  1966.  * Set
  1967.  */
  1968. function enableAecRecording() {
  1969.   dumpCreator.enableAecRecording();
  1970. }
  1971.